@page "/"
@using BlazorReset.Resources
@using BlazorReset.Utils
@using Microsoft.Extensions.Options
@using Newtonsoft.Json.Linq
@using PrimS.Telnet
@using System.Collections.Concurrent
@using System.Diagnostics
@using System.Text.RegularExpressions
@inject NavigationManager Navigation
@inject IDialogService DialogService
@inject ISnackbar Snackbar
@inject Bus Bus;
@inject IOptionsMonitor<ApplicationOptions> ApplicationOptions
@inject HomeState State
@implements IDisposable


<MudTextField Variant="Variant.Outlined"
@bind-value="State.Ip"
Label="@Strings.IP"
Placeholder="@Strings.IpToolTip"
Adornment="Adornment.End"
Immediate
Mask="@RegexMask.IPv4()"
OnAdornmentClick="GotoSearchPage"
AdornmentIcon="@Icons.Material.Filled.Search" />

@if (State.DeviceType is DeviceType.Unknown)
{
    <MudTextField Variant="Variant.Outlined"
    @bind-value="State.CustomPassword"
    Label="@Strings.Password"
    Placeholder="@Strings.PasswordTip"
    InputType="@(_isPasswordVisible ? InputType.Text : InputType.Password)"
    Adornment="Adornment.End"
    AdornmentIcon="@( _isPasswordVisible ? Icons.Material.Filled.Visibility : Icons.Material.Filled.VisibilityOff)"
    OnAdornmentClick="TogglePassword"
    Immediate />
}


<MudStack Spacing="0" Class="mt-4">
    <MudText>@Strings.DeviceType：@DeviceTypeDesc()</MudText>
    <MudStack Row="true" Justify="Justify.Center">

        <MudRadioGroup T="DeviceType" @bind-Value="State.DeviceType">
            @foreach (var t in Enum.GetValues<DeviceType>())
            {
                <MudRadio Value="@t">@DeviceTypeEnumName(t)</MudRadio>
            }
        </MudRadioGroup>
    </MudStack>
</MudStack>

<MudStack Row="true" AlignItems="AlignItems.Center" Class="gap-4">
    <MudCheckBox T="bool" @bind-Value="State.ExecuteScriptUponLogin">@Strings.AutoExecScript</MudCheckBox>
    <MudSelect  @bind-Value="State.SelectedScriptFileName">
        @foreach (var sf in State.ScriptFiles??[])
        {
            <MudSelectItem Value="@sf.Name">@sf.Name</MudSelectItem>
        }
    </MudSelect>
    @if (State.SelectedScriptFileName != null)
    {
        <MudButton
        Variant="Variant.Outlined"
        DropShadow="false"
        OnClick="EditScriptFile"

        >
            <MudIcon Icon="@Icons.Material.Filled.Edit" Size="Size.Small" />
            @Strings.edit
        </MudButton>
    }
</MudStack>

@if(State.IsLoggedIn)
{
    <MudStack row Class="flex-wrap my-2">
        @foreach(var cmd in State.Commands??[])
        {
            <MudButton Variant="Variant.Outlined" Size="Size.Small" OnClick="@(()=>ExecuteCmds(cmd))">@cmd.Name</MudButton>
        }
    </MudStack>
}

<MudButton FullWidth="true"
Class="d-flex align-center my-4"
Variant="Variant.Filled"
Color="@(State.IsLoggedIn ? Color.Error : Color.Primary)"
Disabled="@((!State.IsLoggedIn && !CanLogin()) || _isBusy)"
OnClick="@(() => LoginOrOutWithTelnet(State.Ip!))">
    @if (_isBusy)
    {
        <MudProgressCircular Class="mr-2" Style="width: 16px; height: 16px" Indeterminate></MudProgressCircular>
    }
    else
    {
        <MudIcon Icon="@(State.IsLoggedIn ? Icons.Material.Filled.Logout : Icons.Material.Filled.Login )" Class="mr-2" />
    }
    @(State.IsLoggedIn ? Strings.Logout : Strings.Login )
</MudButton>

<MudTextField
Label="@Strings.Log"
@bind-Value="State.Logs"
T="string"
ShrinkLabel
ReadOnly
Variant="Variant.Outlined"
Lines="@(State.IsLoggedIn ? 15 : 20)" />

<MudFab Color="Color.Error"
StartIcon="@Icons.Material.Filled.Delete"
Style="position: fixed; right: 16px; bottom: 16px"
OnClick="ClearLogs" />

@code {
    const string terminator = "#";
    const string pattern = @".\[([0-9](;?[0-9]*))*m";


    string DeviceTypeEnumName(DeviceType deviceType) => deviceType switch
    {
        DeviceType.Old => Strings.DeviceTypeOld,
        DeviceType.New => Strings.DeviceTypeNew,
        _ => Strings.DeviceTypeUnkown
    };

    bool _isPasswordVisible = false;
    string DeviceTypeDesc() => State.DeviceType switch
    {
        DeviceType.Old => "EV300/DV300/CV500",
        DeviceType.New => "DV350(pro)/DV400/EV500",
        _ => Strings.DeviceTypeUnkown
    };

    string? Password() => State.DeviceType switch
    {
        DeviceType.Old => "antslq",
        DeviceType.New => "haantslq",
        _ => State.CustomPassword
    };

    bool CanLogin() => !string.IsNullOrEmpty(Password()) && !string.IsNullOrEmpty(State.Ip);

    bool _isBusy = false;


    int _connectionLostPrompted = 0;


    void TogglePassword() => _isPasswordVisible = !_isPasswordVisible;

    private void GotoSearchPage()
    {
        Navigation.NavigateTo($"/devices");
    }

    async Task LoginOrOutWithTelnet(string ip)
    {
        State.Cts?.CancelAsync();
        if (State.IsLoggedIn)
        {
            State.IsLoggedIn = false;
        }
        else
        {
            _isBusy = true;
            State.Cts = new CancellationTokenSource();
            var stream = new TcpByteStream(ip, 23);
            var telnet = State.Telnet = new Client(stream, State.Cts.Token);
            var success = await telnet.TryLoginAsync("root", Password()!, 3000, terminator: terminator);
            State.IsLoggedIn = success;
            Interlocked.Exchange(ref _connectionLostPrompted, 0);
            _isBusy = false;
            if (State.IsLoggedIn)
            {
                State.Logs = "";
                _ = ReadWorker(telnet, State.Cts.Token);
                if (State.ExecuteScriptUponLogin && State.SelectedScriptFileName != null)
                {
                    var sf = State.SelectedScriptFile;
                    foreach (var l in sf?.Lines??[])
                    {
                        await telnet.WriteLineAsync(l);
                        await Task.Delay(100);
                    }
                }

            }

        }
    }

    void ClearLogs(){
        State.Logs = "";
    }

    async Task ExecuteCmds(MultilineCmd cmd)
    {
        try
        {
            foreach (var line in cmd.Cmds)
            {
                await State.Telnet!.WriteLineAsync(line);
                await Task.Delay(200);
            }
        }
        catch (IOException)
        {
            State.Cts?.Cancel();
            State.IsLoggedIn = false;
            if (Interlocked.Exchange(ref _connectionLostPrompted, 1) == 0)
            {
                Snackbar.Add(Strings.ConnectionBrokenTip);
            }
        }

    }

    async Task ReadWorker(Client client, CancellationToken token)
    {

        while (!token.IsCancellationRequested)
        {
            try
            {
                var line = await client.TerminatedReadAsync(terminator, timeout: TimeSpan.FromMilliseconds(500));
                if (!string.IsNullOrEmpty(line))
                {
                    line = Regex.Replace(line.Trim(), pattern, "");
                    await this.InvokeAsync(() =>
                               {
                                   State.Logs = $"{line}{Environment.NewLine}" + State.Logs;
                                   StateHasChanged();
                               });
                }
            }
            catch (Exception)
            {
                State.IsLoggedIn = false;
                if (Interlocked.Exchange(ref _connectionLostPrompted, 1) == 0){
                    Snackbar.Add(Strings.ConnectionBrokenTip);
                }
                break;
            }

        }
    }

    private void OnLanguageChanged(object? sender, EventArgs e) => StateHasChanged();

    protected override async Task OnInitializedAsync()
    {
        Bus.LanguageChanged += OnLanguageChanged;


        State.Commands ??= await Utils.Helper.LoadCommandsAsync(); 


        State.ScriptFiles ??= await Utils.Helper.LoadScriptsAsync();
        
    }


    void EditScriptFile()
    {
        var si = new ProcessStartInfo
            {
                FileName = State.SelectedScriptFile?.FullPath,
                UseShellExecute = true
            };
        Process.Start(si);
    }


    public void Dispose()
    {
        Bus.LanguageChanged -= OnLanguageChanged;
    }

}
